/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package javamail.sip;

import java.io.File;
import java.util.Calendar;
import java.util.Hashtable;
import java.util.ListIterator;
import java.util.List;
import java.util.Random;
import javax.media.format.AudioFormat;
import javamail.rtp.rtp;
import javamail.sdp.sdpGenerator;
import javamail.sdp.sdpMessage;
import javamail.sdp.sdpParser;
import javamail.sdp.util.eRtpStaticPayload;
import javamail.sdp.util.eSdpMediaType;
import javamail.sdp.util.mediaLine;
import javamail.sip.util.eSipCallState;
import javamail.sip.util.eSipMethodType;
import javamail.sip.util.eSipRespCode;
import javamail.speech.speechManager;
import javamail.speech.speechMessage;
import javamail.speech.speechParser;

/**
 *
 * @author sriram
 */
public class sipCall {
    private String localHost = "";
    private int localPort = 5060;
    private String contactHost = "";
    private static boolean debugEnabled = true;
    private String callid = null;
    private String peer = null;
    private String peerWithTag = null;
    private String peerHost = "localhost";
    private int peerPort = 5060;
    private String rtpHost = "localhost";
    private int rtpPort = 0;
    private String addressTag = null;
    private int sessId = 0;

    private eSipCallState state = eSipCallState.not_in_call;
    //private List<sipTransaction> outgoingQueue = null;
    private Hashtable<String, sipTransaction> pendingQueue = null;
    private sdpMessage remoteSdp = null;
    private sdpMessage localSdp = null;
    private sipMessage sentResponse = null;
    private rtp rt = null;
    private speechManager speechMgr = null;

    public sipCall(String callid, String from) {
        this.callid = new String(callid);
        peer = new String(from);
        //outgoingQueue = new Vector(1);
        pendingQueue = new Hashtable<String, sipTransaction>();
        addressTag = createToTag();
        sessId = createSessionID();
        localSdp = new sdpMessage();
        localHost = javamail.main.glblConfig.getSipInterface();
        localPort = javamail.main.glblConfig.getSipPort();
        contactHost = localHost;
    }

    public boolean matchesFromTo(String from) {
        boolean result = false;
        //System.err.println("Msg FROM: "+from);
        //System.err.println("Call FROM: "+peerHost);
        if(from.trim().equals(peerHost))
            result = true;
        return result;
    }
    
    public String getCurrMsg() {
        if(javamail.main.glblConfig.getCurrentMessage()!=null) {
            return javamail.main.glblConfig.getCurrentMessage();
        }
        return javamail.main.glblConfig.getDefaultMessage();
    }
    
    public String getDfltMsg() {
        return javamail.main.glblConfig.getDefaultMessage();
    }
    
    public String createToTag() {
        Random rand = new Random();
        Calendar cal = Calendar.getInstance();
        return ";tag=" + rand.nextInt(100)+cal.getTimeInMillis()+"-"+rand.nextLong();
    }
    
    public int createSessionID() {
        Random rand = new Random();
        return rand.nextInt(1000000)+99999;
    }
    
    public void setPeerHostNamePort(sipMessage msg) {
        try {
            if(contactHost.equals("0.0.0.0") || contactHost.startsWith("127.0") ||
                    contactHost.startsWith("localhost")) {
                String to = msg.getToAddress();
                String[] result = to.split(":");
                if(result[0].equals("sip"))
                    contactHost = result[1];
                else
                    contactHost = result[0];
            }

            String[] result = msg.getHostNamePort();
            if(result == null) {
                peerHost = "localhost";
                peerPort = 5060;
            } else if(result.length == 1) {
                peerHost = result[0];
                peerPort = 5060;
            }
            else {
                peerHost = result[0];
                peerPort = Integer.parseInt(result[1]);
            }
        } catch (NumberFormatException e) {
            peerPort = 5060;
        }
    }
    
    public String removeINIP4(String connect) {
        String INIP4 = "IN IP4";
        int index = 0;
        System.out.println(connect);
        if((index = connect.indexOf(INIP4))>=0)
            return connect.substring(index+INIP4.length()).trim();
        else
            return connect;
    }
    
    public String getCallid() {
        return callid;
    }

    public String getPeer() {
        return peer;
    }

    public eSipCallState getState() {
        return state;
    }
    
    public void addTransaction(sipTransaction trans) {
        pendingQueue.put(peer, trans);
    }
    
    public void setTransactionComplete(int index) {
        pendingQueue.remove(peer);
    }
    
    public void changeState(eSipCallState newState) {
        if(debugEnabled)
            System.out.println("sipCall::changeState-Old state: "+ state.toString() +
                    " to New State: "+newState.toString());
        state = newState;
    }
    
    public boolean processInvite(sipMessage msg) {
        boolean result = false;
        switch(state) {
            case not_in_call:
                result = processNewCall(msg);
                break;
            case invite_rcvd:
            case invite_100trying_sent:
            case invite_180ring_sent:
            case invite_200ok_sent:
                processSecondInvite();
                break;
            case invite_sent:
                // Something is wrong, you are receiving an invite
                // for a call that has already sent invite.
                //processGlare();
                break;
            case data:
                // Received a re-invite for an already established call
                processReInvite();
                break;
            default:
                // Ignore the invite for any other state.
                break;
        }
        return result;
    }
    
    public boolean processNewCall(sipMessage msg) {
        boolean result = false;
        if(debugEnabled)
            System.out.println("sipCall::processNewCall-New call has started--");
        
        changeState(eSipCallState.invite_rcvd);
        if(msg.getToUser()==null || 
                !javamail.main.glblConfig.getSipUser().equals(msg.getToUser())) {
            send404NotFound(msg);
            result = true;
        } else {
            // Check if the SIP message contains body (read SDP)
            peerWithTag = msg.getFrom();
            if(msg.containsBody()) {
                sdpParser sdpparser = new sdpParser(msg.body);
                sdpparser.parse();
                remoteSdp = sdpparser.getSdp();
                rtpHost = removeINIP4(remoteSdp.connect);
                ListIterator<mediaLine> rmtMediaList = remoteSdp.mediaList.listIterator();
                mediaLine rmtMediaLn = null;
                while(rmtMediaList.hasNext()) {
                    rmtMediaLn = rmtMediaList.next();
                    if(rmtMediaLn.media==eSdpMediaType.AUDIO) {
                        rtpPort = rmtMediaLn.port;
                        break;
                    }
                }
            }
            send180Ringing(msg);
            send200OK(msg);
        }
        return result;
    }
    
    public void processReInvite() {
        // TODO: update the SDP parameters and modify the connection
        // according to the new INVITE
        // To be implemented later..
    }
    
    public void processSecondInvite() {
        // TODO: Verify that this invite is the same as the previous
        // INVITE. If so, then it is probably a retransmission that
        // we can safely ignore. If not, old INVITE needs to be discarded
        // and the new INVITE will need to be processed
        // To be implemented later..
    }
    public boolean sendBye() {
        boolean result = false;
        switch(state) {
            case data:
                sipGenerator gen = new sipGenerator();
                sipMessage request = new sipMessage();
                request.isRequest = true;
                request.method = eSipMethodType.BYE;
                gen.addVia(request);
                gen.addFromWithTag(request,addressTag);
                gen.addTo(request, peerWithTag);
                gen.addCallId(request, callid);
                gen.addServer(request);
                gen.addCSeq(request, "2 "+eSipMethodType.BYE.name());
                gen.addContact(request, "<sip:"+contactHost+":"+localPort+">");
                gen.addMaxForwards(request);
                gen.addContentLength(request, 0);
                sendResponse(request, peerHost, peerPort);
                changeState(eSipCallState.bye_sent);
                result = true;
                break;
            default:
                break;
        }
        return result;
    }

    public boolean send100Trying() {
        switch(state) {
            case invite_rcvd:
                changeState(eSipCallState.invite_100trying_sent);
                break;
            default:
                break;
        }
        return false;
    }

    public boolean send180Ringing(sipMessage msg) {
        switch(state) {
            case invite_rcvd:
                setPeerHostNamePort(msg); // MOST IMPORTANT - Do this before
                sipGenerator gen = new sipGenerator();
                sipMessage response = new sipMessage();
                response.isRequest = false;
                response.status = eSipRespCode.RINGING;
                gen.addVia(response,msg.getVia());
                gen.addFrom(response, msg.getFrom());
                gen.addTo(response, msg.getTo(), addressTag);
                gen.addCallId(response, msg.getCallid());
                gen.addServer(response);
                gen.addCSeq(response, msg.getCSeq());
                gen.addContact(response, "<sip:"+contactHost+":"+localPort+">");
                gen.addMaxForwards(response);
                gen.addContentLength(response, 0);
                sendResponse(response, peerHost, peerPort);
                changeState(eSipCallState.invite_180ring_sent);
                break;
            default:
                break;
        }
        return false;
    }
    
    public boolean send200OK(sipMessage msg) {
        boolean result = false;
        switch(state) {
            case invite_rcvd:
            case invite_100trying_sent:
            case invite_180ring_sent:
                if(remoteSdp == null) {
                    if(debugEnabled) {
                        System.out.println("sipCall::send200OK-Remote SDP not set!");
                    }
                    break;
                }
                sdpGenerator.createSdp(localSdp,remoteSdp, sessId);
                sipGenerator gen = new sipGenerator();
                sipMessage response = new sipMessage();
                response.isRequest = false;
                response.status = eSipRespCode.OK;
                gen.addVia(response,msg.getVia());
                gen.addFrom(response, msg.getFrom());
                gen.addTo(response, msg.getTo(), addressTag);
                gen.addCallId(response, msg.getCallid());
                gen.addServer(response);
                gen.addContact(response, "<sip:"+contactHost+":"+localPort+">");
                gen.addCSeq(response, msg.getCSeq());
                gen.addMaxForwards(response);
                gen.addContentLength(response, localSdp.toString().getBytes().length);
                gen.addContentType(response, "application/sdp");
                response.addSDP(localSdp);
                setPeerHostNamePort(msg); // MOST IMPORTANT - Do this before
                sendResponse(response, peerHost, peerPort);
                changeState(eSipCallState.invite_200ok_sent);
                break;
            case data:
                // According to RFC 3428 section 7: UAS processing
                if(msg.getCSeq().contains("MESSAGE")) {
                    gen = new sipGenerator();
                    response = new sipMessage();
                    response.isRequest = false;
                    response.status = eSipRespCode.OK;
                    gen.addVia(response,msg.getVia());
                    gen.addFrom(response, msg.getFrom());
                    gen.addTo(response, msg.getTo(), addressTag);
                    gen.addCallId(response, msg.getCallid());
                    gen.addServer(response);
                    gen.addCSeq(response, msg.getCSeq());
                    gen.addMaxForwards(response);
                    gen.addContentLength(response, 0);
                    setPeerHostNamePort(msg); // MOST IMPORTANT - Do this before
                    sendResponse(response, peerHost, peerPort);
                }
                break;
            case bye_sent:
            case bye_rcvd:
                gen = new sipGenerator();
                response = new sipMessage();
                response.isRequest = false;
                response.status = eSipRespCode.OK;
                gen.addVia(response,msg.getVia());
                gen.addFrom(response, msg.getFrom());
                gen.addTo(response, msg.getTo(), addressTag);
                gen.addCallId(response, msg.getCallid());
                gen.addServer(response);
                gen.addContact(response, "<sip:"+contactHost+":"+localPort+">");
                gen.addCSeq(response, msg.getCSeq());
                gen.addMaxForwards(response);
                gen.addContentLength(response, 0);
                setPeerHostNamePort(msg); // MOST IMPORTANT - Do this before
                sendResponse(response, peerHost, peerPort);
                changeState(eSipCallState.bye_200ok_sent);
                // Return true so that call id can be removed from call manager pool
                result = true;
                break;
            default:
                break;
        }
        return result;
    }

    public boolean send404NotFound(sipMessage msg) {
        boolean result = false;
        switch(state) {
            case invite_rcvd:
                setPeerHostNamePort(msg); // MOST IMPORTANT - Do this before
                sipGenerator gen = new sipGenerator();
                sipMessage response = new sipMessage();
                response.isRequest = false;
                response.status = eSipRespCode.NOTFOUND;
                gen.addVia(response,msg.getVia());
                gen.addFrom(response, msg.getFrom());
                gen.addTo(response, msg.getTo(), addressTag);
                gen.addCallId(response, msg.getCallid());
                gen.addServer(response);
                gen.addCSeq(response, msg.getCSeq());
                gen.addContact(response, "<sip:"+contactHost+":"+localPort+">");
                gen.addMaxForwards(response);
                gen.addContentLength(response, 0);
                sendResponse(response, peerHost, peerPort);
                changeState(eSipCallState.invite_404notfound_sent);
                result = true;
                break;
            default:
                break;
        }
        return result;
    }

    public boolean sendMessage(String string, sipMessage msg) {
        boolean result = false;
        switch(state) {
            case data:
                sipGenerator gen = new sipGenerator();
                sipMessage response = new sipMessage();
                response.isRequest = true;
                response.method = eSipMethodType.MESSAGE;
                gen.addVia(response,msg.getVia());
                gen.addFrom(response, msg.getFrom());
                gen.addTo(response, msg.getTo(), addressTag);
                gen.addCallId(response, msg.getCallid());
                gen.addServer(response);
                gen.addCSeq(response, msg.getCSeq());
                gen.addMaxForwards(response);
                gen.addContentLength(response, string.length());
                response.addMessage(string);
                sendResponse(response, peerHost, peerPort);
                result = true;
                break;
            default:
                break;
        }
        return result;
    }
    
    public boolean process200OK(sipMessage msg) {
        boolean result = false;
        switch (state) {
            case bye_sent:
            case bye_rcvd:
                changeState(eSipCallState.bye_200ok_rcvd);
                result = true;
                break;
            default:
                break;
        }
        return result;
    }
    
    public void processAck(sipMessage msg) {
        switch(state) {
            case invite_200ok_sent:
                changeState(eSipCallState.data);
                speechMgr = new speechManager(callid, localHost, (rtpPort+2), rtpHost, rtpPort, getSupportedCodecs(localSdp.getCodecs(eSdpMediaType.AUDIO)));
                //enableVoicePath(localSdp.getCodecs(eSdpMediaType.AUDIO));
                break;
            default:
                // Ignore the invite for any other state.
                break;
        }
    }
    
    public boolean processBye(sipMessage msg) {
        boolean result = false;
        switch(state) {
            case bye_rcvd:
            case cancel_rcvd:
            case not_in_call:
                break;
            case data:
                if(speechMgr!=null) {
                    speechMgr.close();
                    speechMgr = null;
                }
                // *********************************************
                // There is no break here so that it can execute
                // the default code as well
                // *********************************************
            default:
                changeState(eSipCallState.bye_rcvd);
                result = send200OK(msg);
                break;
        }
        return result;
    }

    public void processMessage(sipMessage msg) {
        switch(state) {
            case data:
                if(debugEnabled)
                    System.err.println("sipCall:processMessage()-Received SIP MESSAGE");
                if(msg.containsBody()) {
                    speechParser parser = new speechParser(msg.body);
                    //System.err.println("sipCall:processMessage()-Message has body");
                    if(parser.parse()) {
                        speechMessage speech= parser.getMessage();
                        //System.err.println("sipCall:processMessage()-Parsed SIP MESSAGE:"+speech.toString());
                        send200OK(msg);
                        switch(speech.method) {
                            case START:
                                //speechMgr = new speechManager(callid, localHost, (rtpPort+2), rtpHost, rtpPort, getSupportedCodecs(localSdp.getCodecs(eSdpMediaType.AUDIO)));
                                try {
                                    speechMgr.start();
                                } catch (Exception e) {
                                    System.err.println(e.getStackTrace());
                                }
                                this.sendMessage("Speech Manager started! \n" +
                                        "SYNTHESIZE/RECORD/PLAY/RECOGNIZE/SKIP/START/STOP " +
                                        "are the valid messages", msg);
                                break;
                            case STOP:
                                speechMgr.close();
                                this.sendMessage("Speech Manager stopped! \n" +
                                        "SYNTHESIZE/RECORD/PLAY/RECOGNIZE/SKIP/START/STOP " +
                                        "are the valid messages", msg);
                                break;
                            case NIL:
                                this.sendMessage("Parsing error, check syntax! \n" +
                                        "SYNTHESIZE/RECORD/PLAY/RECOGNIZE/SKIP/START/STOP " +
                                        "are the valid messages", msg);
                                break;
                            default:
                                if(speechMgr!=null && speechMgr.isAlive())
                                    speechMgr.queue(speech);
                                else
                                    this.sendMessage("Speech Manager not started yet! \n" +
                                        "SYNTHESIZE/RECORD/PLAY/RECOGNIZE/SKIP/START/STOP " +
                                        "are the valid messages", msg);
                                break;
                        }
                    } else {
                        this.sendMessage("Parsing error, check syntax! \n" +
                                        "SYNTHESIZE/RECORD/PLAY/RECOGNIZE/SKIP/START/STOP " +
                                        "are the valid messages", msg);
                    }
                }
                break;
            default:
                // Ignore the message for any other state.
                send200OK(msg);
                this.sendMessage("SIP is not in proper state for speech processing! \n" +
                                 "SYNTHESIZE/RECORD/PLAY/RECOGNIZE/SKIP/START/STOP " +
                                 "are the valid messages", msg);
                break;
        }
    }
   
    public void sendResponse(sipMessage msg, String hostname, int port) {
       if(debugEnabled) {
           System.out.println("Sending message to host:"+hostname+" port:"+port);
       }

        String messageToSend = sipGenerator.toString(msg);
        sipMessageSender msgSender = new sipMessageSender(messageToSend, hostname, port);
        msgSender.start();
    }

    public AudioFormat[] getSupportedCodecs(List codecs) {
        // Initialize the Codec Formats to be sent to the RTP transmitter
        AudioFormat[] fmt = new AudioFormat[codecs.size()];
        AudioFormat pcmu = new AudioFormat(AudioFormat.ULAW_RTP, 8000d, 8, 1, AudioFormat.LITTLE_ENDIAN, AudioFormat.SIGNED);
        AudioFormat pcma = new AudioFormat(AudioFormat.ALAW, 8000d, 8,1,AudioFormat.LITTLE_ENDIAN, AudioFormat.SIGNED);
        AudioFormat gsm = new AudioFormat(AudioFormat.GSM, 8000d, 8,1,AudioFormat.LITTLE_ENDIAN, AudioFormat.SIGNED);
        AudioFormat g723 = new AudioFormat(AudioFormat.G723_RTP, 8000d, 8, 1, AudioFormat.LITTLE_ENDIAN, AudioFormat.SIGNED);
        AudioFormat g729 = new AudioFormat(AudioFormat.G729_RTP, 8000d, 8, 1, AudioFormat.LITTLE_ENDIAN, AudioFormat.SIGNED);
        ListIterator list = codecs.listIterator();
        int i=0;
        eRtpStaticPayload payload = null;
        while(list.hasNext()) {
            payload = (eRtpStaticPayload) list.next();
            if(debugEnabled)
                System.out.println("sipCall:getSupportedCodecs-Enabling "+ payload.name() +" payload");
            switch(payload) {
                case PCMA:
                    fmt[i] = pcma;
                    break;
                case PCMU:
                    fmt[i] = pcmu;
                    break;
                case G723:
                    fmt[i] = g723;
                    break;
                case GSM:
                    fmt[i] = gsm;
                    break;
                case G729:
                    fmt[i] = g729;
                    break;
            }
            i++;
        }
        return fmt;
    }
    
    public void enableVoicePath(List codecs) {
        // Initialize the Codec Formats to be sent to the RTP transmitter
        AudioFormat[] fmt = getSupportedCodecs(codecs);
        
        // Initialize the four String arguments to be sent to the 
        // RTP transmitter.
        String msgFile = null;
        String defaultFile = null;
        // First argument is the current message file
        File f = new File(getCurrMsg());
        if(f!=null)
            msgFile = "file:"+f.getAbsolutePath();
        else
            msgFile = "file:/home/sriram/"+getCurrMsg();
        //msgFile = "dsound://0";
        //msgFile = "file://a/oberon-home1/v09/sriramy/bin/record.au";
        // Second argument is the default message file
        f = new File(getDfltMsg());
        if(f!=null)
            defaultFile = "file:"+f.getAbsolutePath();
        else
            defaultFile = "file:/home/sriram/"+getDfltMsg();
        //defaultFile = "file://home/sriram/record.wav";
        //defaultFile = "file://a/oberon-home1/v09/sriramy/bin/record.wav";
        defaultFile = "file://a/oberon-home1/v09/sriramy/bin/record.wav";
        //args[1] = "javasound://0";
        
        // Start the RTP transmission in a new thread. 
        // This warrants a new thread because other calls cannot be handled
        // if we allow transmission to block the message acceptor.
        //rt = new rtp(msgFile, defaultFile,
        //                    localHost, rtpPort+2,
        //                    rtpHost, rtpPort,
        //                    fmt, this);
        //rt.start();
    }
}
